package main

import (
	"sync"
	"io/ioutil"
	"encoding/json"
	"time"
	"github.com/valyala/fasthttp"
	"strings"
	"encoding/base64"
)

const PREFIX = "Basic "

func parseBasicAuth(ctx *fasthttp.RequestCtx) (username, password string, ok bool) {
	auth := string(ctx.Request.Header.Peek("Authorization"))
	if !strings.HasPrefix(auth, PREFIX) {
		return
	}
	c, err := base64.StdEncoding.DecodeString(auth[len(PREFIX):])
	if err != nil {
		return
	}
	cs := string(c)
	s := strings.IndexByte(cs, ':')
	if s < 0 {
		return
	}
	return cs[:s], cs[s+1:], true
}

type Authenticator interface {
	Auth(*fasthttp.RequestCtx) (string,bool)
	Reload() chan error
	Close()
}

type SimpleAuthenticator map[string]string

func (s SimpleAuthenticator) Auth(ctx *fasthttp.RequestCtx) (string,bool) {
	u, p, ok := parseBasicAuth(ctx)
	return u,ok && s[u] == p
}
func (s SimpleAuthenticator) Close() {

}
func (s SimpleAuthenticator) Reload() chan error {
	return nil
}

type JsonAuthenticator struct {
	mu     sync.RWMutex
	fn     string
	users  map[string]string
	close  chan interface{}
	closed chan interface{}
}

func NewJsonAuthenticator(fn string) (*JsonAuthenticator, error) {
	a := &JsonAuthenticator{
		fn:     fn,
		users:  make(map[string]string),
		close:  make(chan interface{}),
		closed: make(chan interface{}),
	}
	return a, a.load()
}

func (a *JsonAuthenticator) Close() {
	close(a.close)
	<-a.closed
}

func (a *JsonAuthenticator) Auth(ctx *fasthttp.RequestCtx)  (string,bool) {
	u, p, ok := parseBasicAuth(ctx)
	if ok {
		a.mu.RLock()
		defer a.mu.RUnlock()
		if p1, ok1 := a.users[u]; ok1 {
			return u,p == p1
		}
	}
	return "",false
}

func (a *JsonAuthenticator) load() error {
	b, err := ioutil.ReadFile(a.fn)
	if err != nil {
		return err
	}
	a.mu.Lock()
	defer a.mu.Unlock()
	return json.Unmarshal(b, a.users)
}

func (a *JsonAuthenticator) Reload() chan error {
	ch := make(chan error)
	tick := time.Tick(time.Second * time.Duration(900))
	go func() {
	LOOP:
		for {
			select {
			case <-tick:
				ch <- a.load()
			case <-a.close:
				break LOOP
			}
		}
		close(a.closed)
	}()
	return ch
}
